package archive

import (
	"encoding/json"
	"fmt"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
)

// OperationStatus represents the status of an archive operation
type OperationStatus string

const (
	StatusWaiting  OperationStatus = "waiting"
	StatusFinished OperationStatus = "finished"
)

// OperationAction represents the type of archive operation
type OperationAction string

const (
	ActionArchive OperationAction = "archive"
	ActionRestore OperationAction = "restore"
	ActionDelete  OperationAction = "delete"
)

// MetadataEntry represents a single archive operation in the metadata file
type MetadataEntry struct {
	Action          OperationAction `json:"action"`
	SourceRefName   string          `json:"source_ref_name"`
	SourceCommitID  string          `json:"source_commit_id"`
	TargetRefName   string          `json:"target_ref_name"`
	CreatorID       int64           `json:"creator_id"`
	CreatorName     string          `json:"creator_name"`
	CreateAt        time.Time       `json:"create_at"`
	UpdateAt        time.Time       `json:"update_at"`
	Status          OperationStatus `json:"status"`
	Reason          string          `json:"reason"`
	ErrorMessage    string          `json:"error_message"`
	ErrorCode       int32           `json:"error_code"`
}

// MetadataFile represents a metadata file containing multiple operations
type MetadataFile struct {
	Entries   []MetadataEntry `json:"entries,omitempty"`
	Timestamp time.Time       `json:"timestamp,omitempty"`
	TaskID    string          `json:"task_id,omitempty"`
	Action    OperationAction `json:"action,omitempty"`
	Refs      []RefInfo       `json:"refs,omitempty"`
	CreatorID int64          `json:"creator_id,omitempty"`
	CreatorName string        `json:"creator_name,omitempty"`
	Reason    string          `json:"reason,omitempty"`
}

// RefInfo represents reference information for waiting/doing metadata format
type RefInfo struct {
	SourceRefName  string `json:"source_ref_name"`
	SourceCommitID string `json:"source_commit_id"`
}

// ToProtoOperation converts MetadataEntry to gRPC ArchiveOperation
func (e *MetadataEntry) ToProtoOperation() *gitalypb.ArchiveOperation {
	var action gitalypb.ArchiveOperation_Action
	switch e.Action {
	case ActionArchive:
		action = gitalypb.ArchiveOperation_ARCHIVE
	case ActionRestore:
		action = gitalypb.ArchiveOperation_RESTORE
	case ActionDelete:
		action = gitalypb.ArchiveOperation_DELETE
	}

	var status gitalypb.ArchiveOperation_Status
	switch e.Status {
	case StatusWaiting:
		status = gitalypb.ArchiveOperation_WAITING
	case StatusFinished:
		status = gitalypb.ArchiveOperation_DONE
	}

	return &gitalypb.ArchiveOperation{
		Action:         action,
		SourceRefName:  e.SourceRefName,
		SourceCommitId: e.SourceCommitID,
		TargetRefName:  e.TargetRefName,
		CreatorId:      e.CreatorID,
		CreatorName:    e.CreatorName,
		CreateAt:       TimeToProtoTimestamp(e.CreateAt),
		UpdateAt:       TimeToProtoTimestamp(e.UpdateAt),
		Status:         status,
		Reason:         e.Reason,
		ErrorMessage:   e.ErrorMessage,
		ErrorCode:      e.ErrorCode,
	}
}

// FromProtoOperation creates MetadataEntry from gRPC ArchiveOperation
func FromProtoOperation(op *gitalypb.ArchiveOperation) *MetadataEntry {
	var action OperationAction
	switch op.Action {
	case gitalypb.ArchiveOperation_ARCHIVE:
		action = ActionArchive
	case gitalypb.ArchiveOperation_RESTORE:
		action = ActionRestore
	case gitalypb.ArchiveOperation_DELETE:
		action = ActionDelete
	}

	var status OperationStatus
	switch op.Status {
	case gitalypb.ArchiveOperation_WAITING:
		status = StatusWaiting
	case gitalypb.ArchiveOperation_DONE:
		status = StatusFinished
	}

	return &MetadataEntry{
		Action:         action,
		SourceRefName:  op.SourceRefName,
		SourceCommitID: op.SourceCommitId,
		TargetRefName:  op.TargetRefName,
		CreatorID:      op.CreatorId,
		CreatorName:    op.CreatorName,
		CreateAt:       ProtoTimestampToTime(op.CreateAt),
		UpdateAt:       ProtoTimestampToTime(op.UpdateAt),
		Status:         status,
		Reason:         op.Reason,
		ErrorMessage:   op.ErrorMessage,
		ErrorCode:      op.ErrorCode,
	}
}

// MetadataPath generates the metadata file path based on ref type and status
func MetadataPath(repoPath, refType string, status OperationStatus, timestamp time.Time, taskID string, action OperationAction) string {
	timestampStr := strconv.FormatInt(timestamp.Unix(), 10)

	var filename string
	if status == StatusWaiting {
		// waiting目录：refs-list-<timestamp>-<task_id>-<action>.json
		filename = fmt.Sprintf("refs-list-%s-%s-%s.json", timestampStr, taskID, action)
	} else {
		// finished目录：refs-list-<timestamp>.json
		filename = fmt.Sprintf("refs-list-%s.json", timestampStr)
	}

	return filepath.Join(repoPath, "info", "archive", "refs", refType, string(status), filename)
}

// ParseRefType extracts ref type from ref name
func ParseRefType(refName string) string {
	parts := strings.Split(strings.TrimPrefix(refName, "refs/"), "/")
	if len(parts) > 0 {
		return parts[0]
	}
	return "unknown"
}

// GenerateArchivedRefName generates archived ref name with commit ID suffix
func GenerateArchivedRefName(originalRef, commitID string) string {
	return fmt.Sprintf("%s-%s", originalRef, commitID)
}

// ParseArchivedRefName extracts original ref name from archived ref name
func ParseArchivedRefName(archivedRef string) (string, string) {
	// Find the last dash that separates ref name from commit ID
	lastDash := strings.LastIndex(archivedRef, "-")
	if lastDash == -1 {
		return archivedRef, ""
	}

	originalRef := archivedRef[:lastDash]
	commitID := archivedRef[lastDash+1:]
	return originalRef, commitID
}

// SerializeMetadata serializes metadata to JSON based on status
func (mf *MetadataFile) SerializeMetadata(status OperationStatus) ([]byte, error) {
	if status == StatusWaiting {
		// waiting目录格式（平铺格式）
		var flatEntries []map[string]interface{}

		for _, entry := range mf.Entries {
			flatEntry := map[string]interface{}{
				"action":           entry.Action,
				"source_ref_name":  entry.SourceRefName,
				"source_commit_id": entry.SourceCommitID,
				"target_ref_name":  entry.TargetRefName,
				"creator_id":       entry.CreatorID,
				"creator_name":     entry.CreatorName,
				"create_at":        entry.CreateAt.Format(time.RFC3339),
				"update_at":        entry.UpdateAt.Format(time.RFC3339),
				"status":           entry.Status,
				"reason":           entry.Reason,
				"error_message":    entry.ErrorMessage,
				"error_code":       entry.ErrorCode,
			}
			flatEntries = append(flatEntries, flatEntry)
		}

		return json.MarshalIndent(flatEntries, "", "  ")
	} else {
		// finished目录格式（list格式）
		return json.MarshalIndent(mf.Entries, "", "  ")
	}
}

// DeserializeMetadata deserializes metadata from JSON
func DeserializeMetadata(data []byte) ([]MetadataEntry, error) {
	var entries []MetadataEntry
	if err := json.Unmarshal(data, &entries); err != nil {
		return nil, fmt.Errorf("failed to deserialize metadata: %w", err)
	}
	return entries, nil
}

// DeserializeWaitingMetadata deserializes waiting metadata from JSON (flat format)
func DeserializeWaitingMetadata(data []byte) ([]MetadataEntry, string, error) {
	var flatEntries []map[string]interface{}
	if err := json.Unmarshal(data, &flatEntries); err != nil {
		return nil, "", fmt.Errorf("failed to deserialize waiting metadata: %w", err)
	}

	var entries []MetadataEntry
	var taskID string

	for i, flat := range flatEntries {
		entry := MetadataEntry{}

		if action, ok := flat["action"].(string); ok {
			switch action {
			case "archive":
				entry.Action = ActionArchive
			case "restore":
				entry.Action = ActionRestore
			case "delete":
				entry.Action = ActionDelete
			}
		}

		if sourceRefName, ok := flat["source_ref_name"].(string); ok {
			entry.SourceRefName = sourceRefName
		}

		if sourceCommitID, ok := flat["source_commit_id"].(string); ok {
			entry.SourceCommitID = sourceCommitID
		}

		if targetRefName, ok := flat["target_ref_name"].(string); ok {
			entry.TargetRefName = targetRefName
		}

		if creatorID, ok := flat["creator_id"].(float64); ok {
			entry.CreatorID = int64(creatorID)
		}

		if creatorName, ok := flat["creator_name"].(string); ok {
			entry.CreatorName = creatorName
		}

		if reason, ok := flat["reason"].(string); ok {
			entry.Reason = reason
		}

		if createAtStr, ok := flat["create_at"].(string); ok {
			if createAt, err := time.Parse(time.RFC3339, createAtStr); err == nil {
				entry.CreateAt = createAt
			}
		}

		if updateAtStr, ok := flat["update_at"].(string); ok {
			if updateAt, err := time.Parse(time.RFC3339, updateAtStr); err == nil {
				entry.UpdateAt = updateAt
			}
		}

		if status, ok := flat["status"].(string); ok {
			switch status {
			case "waiting":
				entry.Status = StatusWaiting
			case "finished":
				entry.Status = StatusFinished
			}
		}

		if errorMessage, ok := flat["error_message"].(string); ok {
			entry.ErrorMessage = errorMessage
		}

		if errorCode, ok := flat["error_code"].(float64); ok {
			entry.ErrorCode = int32(errorCode)
		}

		// Extract taskID from the first entry if not already set
		if i == 0 && taskID == "" {
			// Generate a taskID based on action, creator, and timestamp
			taskID = fmt.Sprintf("%s-%d-%d", entry.Action, entry.CreatorID, entry.CreateAt.Unix())
		}

		entries = append(entries, entry)
	}

	return entries, taskID, nil
}

// DeserializeMetadataByFormat automatically detects and deserializes metadata
func DeserializeMetadataByFormat(data []byte) ([]MetadataEntry, string, error) {
	// Try to parse as array first (both flat waiting format and finished format use array)
	var testArray []interface{}
	if err := json.Unmarshal(data, &testArray); err != nil {
		return nil, "", fmt.Errorf("failed to parse JSON: %w", err)
	}

	if len(testArray) == 0 {
		return []MetadataEntry{}, "", nil
	}

	// Check if first element is a flat object (waiting format) or MetadataEntry object (finished format)
	firstElement := testArray[0]
	if elementMap, ok := firstElement.(map[string]interface{}); ok {
		// Check if it has the structure of a flat waiting format
		if _, hasAction := elementMap["action"]; hasAction {
			if _, hasSourceRef := elementMap["source_ref_name"]; hasSourceRef {
				// It's waiting format (flat)
				return DeserializeWaitingMetadata(data)
			}
		}
	}

	// It's finished format (list)
	entries, err := DeserializeMetadata(data)
	return entries, "", err
}
